package emulator.hardware.io.peripherals.iec;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import emulator.hardware.CPU;
import emulator.hardware.HwByte;
import emulator.hardware.clock.Clock;
import emulator.hardware.clock.ClockHandle;
import emulator.hardware.io.peripherals.IecPort;
import emulator.hardware.io.peripherals.iec.sim.Action;
import emulator.hardware.io.peripherals.iec.sim.CompoundState;
import emulator.hardware.io.peripherals.iec.sim.Condition;
import emulator.hardware.io.peripherals.iec.sim.Edge;
import emulator.hardware.io.peripherals.iec.sim.Effect;
import emulator.hardware.io.peripherals.iec.sim.Event;
import emulator.hardware.io.peripherals.iec.sim.IecWorld;
import emulator.hardware.io.peripherals.iec.sim.StateMachine;
import emulator.hardware.io.peripherals.iec.sim.MachineState;

public class IecSim extends Thread
{
	static Logger logger = LogManager.getLogger(IecSim.class.getName());
	private static final int T_VALID = 70;
	private static final int T_SETUP = 70;
	private static final int RCVBUF_MAX = 65536;
	private ClockHandle clock = null;
	protected IecWorld iecWorld = null;
	private IecLog log = null;
	private boolean is_state_log_enabled = false;
	private ByteBuffer buffer = ByteBuffer.allocate(RCVBUF_MAX);
	private HashMap<Integer, IecSimDevice> device_map = null;
	private IecSimDevice active_device = null;
	private IecSimDeviceFactory factory = new IecSimDeviceFactory();
	private long lastClock;
	private int portLastValue = 0;
	
	private StateMachine _iecMachine = null;
	private CPU _cpu;
	protected int _nextToSend;

	public IecSim(CPU cpu)
	{
		super();
		_cpu = cpu;
		log = new IecLog();
		initDevices();
		IecSimDeviceRegistry.instance().addObserver(new DeviceRegistryObserver());
	}

	private synchronized void initDevices()
	{
		logger.entry();
		active_device = null;
		device_map = new HashMap<Integer, IecSimDevice>();
		int device_numbers[] = IecSimDeviceRegistry.instance().enumDevices();
		for (int i = 0; i < device_numbers.length; i++)
		{
			DeviceInfo device_info = IecSimDeviceRegistry.instance().getDevice(device_numbers[i]);
			if (device_info != null)
			{
				IecSimDevice device = factory.createDevice(device_info);
				if (device != null)
					addDevice(device, device_info.getAddress());
			}
		}
	}

	private void addDevice(IecSimDevice device, int address)
	{
		logger.info("Adding device #" + address + " " + device);
		device_map.put(address, device);
		device.setIecSim(this);
	}
	
	public void attach(Clock clock, IecPort port)
	{
		if (clock != null)
			this.clock = clock.acquireHandle();
		iecWorld = new IecWorld(port);
		lastClock = this.clock.getTicks();
		initMachine();
	}
	
	private class LogEffect implements Effect
	{
		private String _text;
		LogEffect(String text) { _text = text; }
		@Override public void execute() { logState(_text);}
	}
	
	private class ReportByteEffect implements Effect
	{
		private String _direction;
		private boolean _isAtn;
		
		ReportByteEffect(String direction, boolean isAtn) { _direction = direction; _isAtn = isAtn; }
		
		@Override
		public void execute() { reportByte(_direction, _isAtn, iecWorld.getDataByte()); }
	}
	
	private class ProcessBuffer implements Effect
	{
		private boolean _isAtn;
		
		ProcessBuffer(boolean isAtn) { _isAtn = isAtn; }

		@Override
		public void execute() { buffer.flip(); processData(_isAtn); }
	}
	
	Effect logEffect(String text) { return new LogEffect(text); }
	Action logAction(String text) { return new Action(new Effect [] { new LogEffect(text) }); }
	Effect logData(String direction, boolean isAtn) { return new ReportByteEffect(direction, isAtn); }
	Effect clearBuffer() { return new Effect() { @Override public void execute() { buffer.clear(); } }; }
	Effect putData() { return new Effect() { @Override public void execute() { 
		buffer.put((byte) iecWorld.getDataByte()); 
		} }; }
	Effect processBuffer(boolean isAtn) { return new ProcessBuffer(isAtn); }
	Effect initSend() { return new Effect() { @Override public void execute() {
		logger.debug("initSend for device "+active_device);
		iecWorld.setDataByte(active_device != null ? active_device.read() : -1); 
		} }; }
	Effect brk() { return new Effect() { @Override public void execute() { log("<BRK>"); _cpu.halt(); } }; }
	Condition hasToSend() { return new Condition() { @Override public boolean evaluate() { return iecWorld.getDataByte() >= 0; } }; }
	Effect peekNext() { return new Effect() { @Override public void execute() { _nextToSend = active_device.read(); iecWorld.SET_EOI(_nextToSend < 0).execute(); } }; }
	Effect sendNext() { return new Effect() { @Override public void execute() { iecWorld.setDataByte(_nextToSend); } }; }
	Condition deviceActive() { return new Condition() { @Override public boolean evaluate() { return active_device != null; } }; }

	public void initMachine()
	{
		MachineState Attention = initAtnMachine();
		MachineState Talk = initTalkMachine();
		MachineState Listen = initListenMachine();
		
		MachineState Idle = new MachineState("IDLE")
			.addEdge(new Event(new Condition [] { iecWorld.ATN }),
							   logAction("IDL->ATN"),
							   Attention);
		
		Attention
			.addEdge(new Event(new Condition [] { iecWorld.nATN, iecWorld.TALK }), logAction("ATN->TLK"), Talk)
			.addEdge(new Event(new Condition [] { iecWorld.nATN, iecWorld.LISTEN }), logAction("ATN->LSN"), Listen)
			.addEdge(new Event(new Condition [] { iecWorld.nATN }),
					           new Action(new Effect[] { logEffect("ATN->IDL"), iecWorld.RELEASE_CLK, iecWorld.RELEASE_DATA }),
					           Idle)
			;
		Talk
			.addEdge(new Event(new Condition [] { iecWorld.ATN }), logAction("TLK->ATN"), Attention)
			.addEdge(new Event(new Condition [] { iecWorld.nTALK, iecWorld.LISTEN }), logAction("TLK->LSN"), Listen)
			.addEdge(new Event(new Condition [] { iecWorld.nTALK }),
							   new Action(new Effect[] { logEffect("TLK->IDL"), iecWorld.RELEASE_CLK, iecWorld.RELEASE_DATA }),
							   Idle)
			;
		Listen
			.addEdge(new Event(new Condition [] { iecWorld.ATN }), logAction("LSN->ATN"), Attention)
			.addEdge(new Event(new Condition [] { iecWorld.nLISTEN, iecWorld.TALK }), logAction("LSN->TLK"), Talk)
			.addEdge(new Event(new Condition [] { iecWorld.nLISTEN }), 
					           new Action(new Effect[] { logEffect("LSN->IDL"), iecWorld.RELEASE_CLK, iecWorld.RELEASE_DATA }),
					           Idle)
			;
		
		_iecMachine = new StateMachine(
				new Edge(IecWorld.TRUE,
						 new Action(new Effect [] { iecWorld.RELEASE_CLK, iecWorld.RELEASE_DATA, iecWorld.END_RECEIVE, iecWorld.END_SEND,
								 					logEffect("INIT->IDL") }), 
						 Idle
				)
			);
	}

	public CompoundState initListenMachine()
	{
		MachineState initListen = new MachineState("LSN:INIT");
		MachineState waitForStart = new MachineState("LSN:WAIT_STX");
		MachineState waitForClock = new MachineState("LSN:WAIT_CLK");
		MachineState waitEoiHandshake = new MachineState("LSN:WAIT_EOI");
		MachineState waitForClock2 = new MachineState("LSN:WAIT_CLK2");
		MachineState waitBit0 = new MachineState("LSN:WAIT_BIT0");
		MachineState gotBit0 = new MachineState("LSN:GOT_BIT0");
		MachineState waitBit1 = new MachineState("LSN:WAIT_BIT1");
		MachineState gotBit1 = new MachineState("LSN:GOT_BIT1");
		MachineState waitBit2 = new MachineState("LSN:WAIT_BIT2");
		MachineState gotBit2 = new MachineState("LSN:GOT_BIT2");
		MachineState waitBit3 = new MachineState("LSN:WAIT_BIT3");
		MachineState gotBit3 = new MachineState("LSN:GOT_BIT3");
		MachineState waitBit4 = new MachineState("LSN:WAIT_BIT4");
		MachineState gotBit4 = new MachineState("LSN:GOT_BIT4");
		MachineState waitBit5 = new MachineState("LSN:WAIT_BIT5");
		MachineState gotBit5 = new MachineState("LSN:GOT_BIT5");
		MachineState waitBit6 = new MachineState("LSN:WAIT_BIT6");
		MachineState gotBit6 = new MachineState("LSN:GOT_BIT6");
		MachineState waitBit7 = new MachineState("LSN:WAIT_BIT7");
		MachineState gotBit7 = new MachineState("LSN:GOT_BIT7");
		MachineState endFrame = new MachineState("LSN:END_FRM");
		MachineState finish = new MachineState("LSN:FIN");
		
		initListen
			.addEdge(new Event(new Condition[] { iecWorld.nDATA }),
					 new Action(new Effect[] { logEffect("LSN:->waitSTX"), iecWorld.PULL_DATA, iecWorld.SET_EOI(false) }),
					 waitForStart)
			;
		waitForStart
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					new Action(new Effect[] { logEffect("LSN:waitSTX->waitClk"), iecWorld.RELEASE_DATA, iecWorld.SET_TIMEOUT(256) }),
					 waitForClock)
			;
		waitForClock
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:waitClk->wb0") }),
					 waitBit0)
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("LSN:waitClk->waitEOI"), iecWorld.PULL_DATA, iecWorld.SET_EOI(true), iecWorld.SET_TIMEOUT(60) }),
					 waitEoiHandshake)
			;
		waitEoiHandshake
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("LSN:waitEoi->waitClk2"), iecWorld.RELEASE_DATA }),
					 waitForClock2)
			;
		waitForClock2
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:waitClk2->wb0") }),
					 waitBit0)
			;
		waitBit0
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("LSN:wb0->gb0"), iecWorld.RCV_BIT(0) }),
					 gotBit0)
			;
		gotBit0
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:gb0->wb1") }),
					 waitBit1)
			;
		waitBit1
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("LSN:wb1->gb1"), iecWorld.RCV_BIT(1) }),
					 gotBit1)
			;
		gotBit1
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:gb1->wb2") }),
					 waitBit2)
			;
		waitBit2
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("LSN:wb2->gb2"), iecWorld.RCV_BIT(2) }),
					 gotBit2)
			;
		gotBit2
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:gb2->wb3") }),
					 waitBit3)
			;
		waitBit3
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("LSN:wb3->gb3"), iecWorld.RCV_BIT(3) }),
					 gotBit3)
			;
		gotBit3
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:gb3->wb4") }),
					 waitBit4)
			;
		waitBit4
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("LSN:wb4->gb4"), iecWorld.RCV_BIT(4) }),
					 gotBit4)
			;
		gotBit4
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:gb4->wb5") }),
					 waitBit5)
			;
		waitBit5
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("LSN:wb5->gb5"), iecWorld.RCV_BIT(5) }),
					 gotBit5)
			;
		gotBit5
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:gb5->wb6") }),
					 waitBit6)
			;
		waitBit6
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("LSN:wb6->gb6"), iecWorld.RCV_BIT(6) }),
					 gotBit6)
			;
		gotBit6
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:gb6->wb7") }),
					 waitBit7)
			;
		waitBit7
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("LSN:wb7->gb7"), iecWorld.RCV_BIT(7), logData("(IN)", false), putData() }),
					 gotBit7)
			;
		gotBit7
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("LSN:gb7->endFrm"), iecWorld.PULL_DATA, iecWorld.SET_TIMEOUT(60) }),
					 endFrame)
			;
		endFrame
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT, iecWorld.EOI }),
					 new Action(new Effect[] { logEffect("LSN:endFrm->fin"), 
							 				   iecWorld.RELEASE_DATA,
							 				   processBuffer(false) }),
					 finish)
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("LSN:endFrm->initListen"), 
							 				   iecWorld.RELEASE_DATA }),
					 initListen)
			;
		
		return new CompoundState("LISTEN", new StateMachine(new Edge(IecWorld.TRUE, 
				new Action(new Effect[] { logEffect("LSN:INIT"), clearBuffer(), iecWorld.RELEASE_DATA }),
				initListen)));
	}

	public CompoundState initTalkMachine()
	{
		MachineState talkAtnRelease = new MachineState("TLK:turnAround");
		MachineState initTalk = new MachineState("TLK:init");
		MachineState noData = new MachineState("TLK:noData");
		MachineState waitClkIdle = new MachineState("waitClkIdle");
		MachineState waitStartDataLow = new MachineState("TLK:waitStartDataLow");
		MachineState deviceNotPresent = new MachineState("TLK:deviceNotPresent");
		MachineState waitStartDataHigh = new MachineState("TLK:waitStartDataHigh");
		MachineState signalEoi = new MachineState("TLK:signalEoi");
		MachineState waitStartDataHigh2 = new MachineState("TLK:waitStartDataHigh2");
		MachineState waitStartDataHigh3 = new MachineState("TLK:waitStartDataHigh3");
		MachineState setupBit0 = new MachineState("TLK:setupBit0");
		MachineState holdBit0 = new MachineState("TLK:holdBit0");
		MachineState setupBit1 = new MachineState("TLK:setupBit1");
		MachineState holdBit1 = new MachineState("TLK:holdBit1");
		MachineState setupBit2 = new MachineState("TLK:setupBit2");
		MachineState holdBit2 = new MachineState("TLK:holdBit2");
		MachineState setupBit3 = new MachineState("TLK:setupBit3");
		MachineState holdBit3 = new MachineState("TLK:holdBit3");
		MachineState setupBit4 = new MachineState("TLK:setupBit4");
		MachineState holdBit4 = new MachineState("TLK:holdBit4");
		MachineState setupBit5 = new MachineState("TLK:setupBit5");
		MachineState holdBit5 = new MachineState("TLK:holdBit5");
		MachineState setupBit6 = new MachineState("TLK:setupBit6");
		MachineState holdBit6 = new MachineState("TLK:holdBit6");
		MachineState setupBit7 = new MachineState("TLK:setupBit7");
		MachineState holdBit7 = new MachineState("TLK:holdBit7");
		MachineState waitAck = new MachineState("TLK:waitAck");
		MachineState waitBetweenFrames = new MachineState("TLK:waitBeweenFrames");

		talkAtnRelease	/* wait */
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:turnAround->initTalk"), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(0) }),
					 initTalk)
		 	;
		
		initTalk
			.addEdge(new Event(new Condition[] { hasToSend() }),
					 new Action(new Effect[] { logEffect("TLK:init->waitClkIdle") }),
					 waitClkIdle)
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:init->noData") }),
					 noData)
			;
		waitClkIdle
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("waitClkIdle->waitStartDataLow"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(256) }),
					 waitStartDataLow)
			;
		waitStartDataLow
			.addEdge(new Event(new Condition[] { iecWorld.DATA }),
					 new Action(new Effect[] { logEffect("TLK:waitStartDataLow->waitStartDataHigh"), iecWorld.RELEASE_CLK, peekNext(), logData("(OUT)", false) }),
					 waitStartDataHigh)
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:waitStartDataLow->deviceNotPresent"), iecWorld.RELEASE_CLK, brk() }),
					 deviceNotPresent)
			;
		waitStartDataHigh
			.addEdge(new Event(new Condition[] { iecWorld.nDATA, iecWorld.EOI }),
					 new Action(new Effect[] { logEffect("TLK:waitStartDataHigh->signalEoi") }),
					 signalEoi)
			.addEdge(new Event(new Condition[] { iecWorld.nEOI }),
					 new Action(new Effect[] { logEffect("TLK:waitStartDataHigh->waitStartDataHigh2"), iecWorld.SET_TIMEOUT(25) }),
					 waitStartDataHigh2)
			;
		signalEoi
			.addEdge(new Event(new Condition[] { iecWorld.DATA }),
					 new Action(new Effect[] { logEffect("TLK:signalEoi->waitStartDataHigh2"), iecWorld.SET_TIMEOUT(25) }),
					 waitStartDataHigh2)
			 ;
		waitStartDataHigh2
			.addEdge(new Event(new Condition[] { iecWorld.nDATA }),
					 new Action(new Effect[] { logEffect("TLK:waitStartDataHigh2->setupBit0"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit0)
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }), new Action(new Effect [] {}), waitStartDataHigh3)
			;
		waitStartDataHigh3
			.addEdge(new Event(new Condition[] { iecWorld.nDATA }),
					 new Action(new Effect[] { logEffect("TLK:waitStartDataHigh3->setupBit0"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit0)
			;
		setupBit0
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:setupBit0->holdBit0"), iecWorld.SND_BIT(0), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(T_VALID) }),
					 holdBit0)
			;
		holdBit0
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:holdBit0->setupBit1"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit1)
			;
		setupBit1
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:setupBit1->holdBit1"), iecWorld.SND_BIT(1), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(T_VALID) }),
					 holdBit1)
			;
		holdBit1
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:holdBit1->setupBit2"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit2)
			;
		setupBit2
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:setupBit2->holdBit2"), iecWorld.SND_BIT(2), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(T_VALID) }),
					 holdBit2)
		    ;
		holdBit2
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:holdBit2->setupBit3"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit3)
			;
		setupBit3
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:setupBit3->holdBit3"), iecWorld.SND_BIT(3), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(T_VALID) }),
					 holdBit3)
			;
		holdBit3
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:holdBit3->setupBit4"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit4)
			;
		setupBit4
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:setupBit4->holdBit4"), iecWorld.SND_BIT(4), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(T_VALID) }),
					 holdBit4)
			;
		holdBit4
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:holdBit4->setupBit5"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit5)
			;
		setupBit5
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:setupBit5->holdBit5"), iecWorld.SND_BIT(5), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(T_VALID) }),
					 holdBit5)
			;
		holdBit5
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:holdBit5->setupBit6"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit6)
			;
		setupBit6
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:setupBit6->holdBit6"), iecWorld.SND_BIT(6), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(T_VALID) }),
					 holdBit6)
			;
		holdBit6
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:holdBit6->setupBit7"), iecWorld.PULL_CLK, iecWorld.SET_TIMEOUT(T_SETUP) }),
					 setupBit7)
			;
		setupBit7
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:setupBit7->holdBit7"), iecWorld.SND_BIT(7), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(T_VALID) }),
					 holdBit7)
			;
		holdBit7
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:holdBit7->waitAck"), iecWorld.PULL_CLK, iecWorld.RELEASE_DATA }),
					 waitAck)
			;
		waitAck
			.addEdge(new Event(new Condition[] { iecWorld.DATA }),
					 new Action(new Effect[] { logEffect("TLK:waitAck->waitBetweenFrames"), iecWorld.SET_TIMEOUT(200) }),
					 waitBetweenFrames)
			;
		waitBetweenFrames
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("TLK:waitBetweenFrames->waitStartDataHigh"), sendNext(), iecWorld.RELEASE_CLK, peekNext(), logData("(OUT)", false) }),
					 waitStartDataHigh)
			.addEdge(new Event(new Condition[] { iecWorld.EOI }),
					 new Action(new Effect[] { logEffect("TLK:waitBetweenFrames->noData"), iecWorld.END_SEND, iecWorld.RELEASE_CLK }),
					 noData)
			.addEdge(new Event(new Condition[] { iecWorld.nDATA }),
					 new Action(new Effect[] { logEffect("TLK:waitBetweenFrames->waitStartDataLow"), sendNext(), iecWorld.RELEASE_CLK, iecWorld.SET_TIMEOUT(2000) }),
					 waitStartDataLow)
			;

		return new CompoundState("TALK", new StateMachine(
				new Edge(IecWorld.TRUE,
						 new Action(new Effect [] { logEffect("TLK:->talkAtnRelease"), initSend(), iecWorld.SET_TIMEOUT(100), iecWorld.PULL_CLK, iecWorld.RELEASE_DATA }), 
						 talkAtnRelease
						)
				));
	}

	public CompoundState initAtnMachine()
	{
		MachineState initAtn = new MachineState("ATN:INIT");
		MachineState waitForStart = new MachineState("ATN:WAIT_STX");
		MachineState waitForClock = new MachineState("ATN:WAIT_CLK");
		MachineState waitBit0 = new MachineState("ATN:WAIT_BIT0");
		MachineState gotBit0 = new MachineState("ATN:GOT_BIT0");
		MachineState waitBit1 = new MachineState("ATN:WAIT_BIT1");
		MachineState gotBit1 = new MachineState("ATN:GOT_BIT1");
		MachineState waitBit2 = new MachineState("ATN:WAIT_BIT2");
		MachineState gotBit2 = new MachineState("ATN:GOT_BIT2");
		MachineState waitBit3 = new MachineState("ATN:WAIT_BIT3");
		MachineState gotBit3 = new MachineState("ATN:GOT_BIT3");
		MachineState waitBit4 = new MachineState("ATN:WAIT_BIT4");
		MachineState gotBit4 = new MachineState("ATN:GOT_BIT4");
		MachineState waitBit5 = new MachineState("ATN:WAIT_BIT5");
		MachineState gotBit5 = new MachineState("ATN:GOT_BIT5");
		MachineState waitBit6 = new MachineState("ATN:WAIT_BIT6");
		MachineState gotBit6 = new MachineState("ATN:GOT_BIT6");
		MachineState waitBit7 = new MachineState("ATN:WAIT_BIT7");
		MachineState gotBit7 = new MachineState("ATN:GOT_BIT7");
		MachineState endFrame = new MachineState("ATN:END_FRM");
		MachineState finish = new MachineState("ATN:FIN");
		
		initAtn
			.addEdge(new Event(new Condition [] { iecWorld.CLK }),
					 new Action(new Effect [] { logEffect("ATN:->waitSTX"), iecWorld.PULL_DATA }),
					 waitForStart)
			;
		waitForStart
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					new Action(new Effect [] { logEffect("ATN:waitSTX->waitClk"), iecWorld.RELEASE_DATA }),
					 waitForClock)
			;
		waitForClock
			.addEdge(new Event(new Condition [] { iecWorld.CLK }),
					 new Action(new Effect [] { logEffect("ATN:waitClk->wb0") }),
					 waitBit0)
			;
		waitBit0
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("ATN:wb0->gb0"), iecWorld.RCV_BIT(0) }),
					 gotBit0)
			;
		gotBit0
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("ATN:gb0->wb1") }),
					 waitBit1)
			;
		waitBit1
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("ATN:wb1->gb1"), iecWorld.RCV_BIT(1) }),
					 gotBit1)
			;
		gotBit1
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("ATN:gb1->wb2") }),
					 waitBit2)
			;
		waitBit2
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("ATN:wb2->gb2"), iecWorld.RCV_BIT(2) }),
					 gotBit2)
			;
		gotBit2
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("ATN:gb2->wb3") }),
					 waitBit3)
			;
		waitBit3
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("ATN:wb3->gb3"), iecWorld.RCV_BIT(3) }),
					 gotBit3)
			;
		gotBit3
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("ATN:gb3->wb4") }),
					 waitBit4)
			;
		waitBit4
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("ATN:wb4->gb4"), iecWorld.RCV_BIT(4) }),
					 gotBit4)
			;
		gotBit4
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("ATN:gb4->wb5") }),
					 waitBit5)
			;
		waitBit5
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("ATN:wb5->gb5"), iecWorld.RCV_BIT(5) }),
					 gotBit5)
			;
		gotBit5
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("ATN:gb5->wb6") }),
					 waitBit6)
			;
		waitBit6
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("ATN:wb6->gb6"), iecWorld.RCV_BIT(6) }),
					 gotBit6)
			;
		gotBit6
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("ATN:gb6->wb7") }),
					 waitBit7)
			;
		waitBit7
			.addEdge(new Event(new Condition[] { iecWorld.nCLK }),
					 new Action(new Effect[] { logEffect("ATN:wb7->gb7"), 
							 				   iecWorld.RCV_BIT(7), 
							 				   logData("(IN)", true), 
							 				   new Effect() { @Override public void execute() { checkCommand(iecWorld.getDataByte()); } } }),
					 gotBit7)
			;
		gotBit7
			.addEdge(new Event(new Condition[] { iecWorld.CLK }),
					 new Action(new Effect[] { logEffect("ATN:gb7->endFrm"), iecWorld.PULL_DATA, iecWorld.SET_TIMEOUT(60) }),
					 endFrame)
			;
		endFrame
			.addEdge(new Event(new Condition[] { iecWorld.TIMEOUT }),
					 new Action(new Effect[] { logEffect("ATN:endFrm->finish"), 
							 				   iecWorld.RELEASE_DATA, 
							 				   putData(), processBuffer(true), clearBuffer() }),
					 finish)
			;
		finish
			.addEdge(new Event(new Condition[] { deviceActive() }), logAction("ATN:finish->initAtn"), initAtn)
			;
		
		return new CompoundState("ATN", new StateMachine(
				new Edge(IecWorld.TRUE,
						 new Action(new Effect [] { iecWorld.RELEASE_CLK, iecWorld.RELEASE_DATA, iecWorld.END_RECEIVE, iecWorld.END_SEND,
								 					logEffect("ATN:INIT"), clearBuffer(), iecWorld.SET_EOI(false) }), 
						 initAtn
						)
				));
	}
	
	public void detach()
	{
		this.clock = null;
	}

	@Override
	public void run()
	{
		_iecMachine.reset();
		try {
			for (;;)
			{
				clock.tick();
				iecWorld.tick();
				_iecMachine.tick();
				trackPort();
			}
		}
		catch (Exception ex)
		{
			logger.error("Unexpected failure in IEC simulation", ex);
		}
	}

	private void trackPort()
	{
		if (getPortValue() != portLastValue)
		{
			if ((clock.getTicks() - lastClock) > 0)
				logState("CHG");
		}
	}

	public int getPortValue() {
		return (iecWorld.getAtnIn() ? 1 : 0)
					  | (iecWorld.getClkIn() ? 2 : 0)
					  | (iecWorld.getDataIn() ? 4 : 0);
	}

	private void processData(boolean is_atn) 
	{
		logger.entry();
		if (active_device != null)
		{
			if (is_atn)
				active_device.processCommand(buffer);
			else 
				active_device.processData(buffer);
		}
	}

	private void checkCommand(int cmd)
	{
		logger.debug("checking command "+new HwByte(cmd)+"h, active device="+active_device);
		if ((cmd & 0xE0) == 0x20 || (cmd & 0xE0) == 0x40)
		{
			if (cmd == 0x3f || cmd == 0x5f) // UNLISTEN/UNTALK
			{
				if (active_device != null)
				{
					if (cmd == 0x3f)
						active_device.unlisten();
					else
						active_device.untalk();
					active_device = null;
				}
			}
			else // LISTEN/TALK
			{
				active_device = null;
				int address = cmd & 0x1F;
				if (device_map.containsKey(address))
				{
					active_device = device_map.get(address);
					logger.debug("active device for command "+new HwByte(cmd)+"h ="+active_device);
					if ((cmd & 0xE0) == 0x20)
						active_device.listen();
					else
						active_device.talk();
				}
			}
		}
	}

	protected void reportByte(String dir, boolean is_atn, int data) {
		String log_string
			= dir + " "
			+ (is_atn ? "/" : " ") + new HwByte(data)
			+ " - ";
		if (is_atn)
		{
			if (data >= 0x20 && data < 0x3F)
				log_string += "LISTEN #" + (data & 0x1F);
			else if (data == 0x3F)
				log_string += "UNLISTEN";
			else if (data >= 0x40 && data < 0x5F)
				log_string += "TALK #" + (data & 0x1f);
			else if (data == 0x5F)
				log_string += "UNTALK";
			else if (data >= 0x60 && data < 0x70)
				log_string += "OPEN CHN/DATA #" + (data & 0x0f);
			else if (data >= 0xE0 && data < 0xF0)
				log_string += "CLOSE #" + (data & 0x0f);
			else if (data >= 0xF0)
				log_string += "OPEN #" + (data & 0x0f);
			else
				log_string += "???";
						
		}
		else if (data >= 32 && data < 128)
			log_string += "\'" + (char)data + "\'";
		log(log_string);
	}

	protected void logState(String text)
	{
		portLastValue = getPortValue();
		if (is_state_log_enabled)
		{
			String state =
				"A:" + (iecWorld.getAtnIn() ? "T" : "F") +
				" C:" + (iecWorld.getClkIn() ? "T" : "F") +
				" D:" + (iecWorld.getDataIn() ? "T" : "F") +
				" T(" + (clock.getTicks() - lastClock) + ")" +
				" - " + text;
			log(state);
			lastClock = clock.getTicks();
		}
	}

	void log(String line)
	{
		log.writeln(line);
	}

	public void addLogObserver(Observer o)
	{
		log.addObserver(o);
	}

	public void deleteLogObserver(Observer o)
	{
		log.deleteObserver(o);
	}

	public String getLogLine() {
		return log.getLogLine();
	}

	public void setStateLogEnabled(boolean enabled) {
		is_state_log_enabled = enabled;
	}
	
	public boolean isStateLogEnabled()
	{
		return is_state_log_enabled;
	}

	public class DeviceRegistryObserver implements Observer {

		@Override
		public void update(Observable arg0, Object arg1) {
			initDevices();
		}
	}

	public void initSend(IecSimDevice iecSimDevice)
	{
		logger.debug("initSend from device "+iecSimDevice);
		iecWorld.BEGIN_SEND.execute();
	}

	public void initReceive(IecSimDevice iecSimDevice)
	{
		logger.debug("initReceive from device "+iecSimDevice);
		iecWorld.BEGIN_RECEIVE.execute();
	}
}
